package com.fabula.android.timeline.XMPP;

import java.util.*;

import org.jivesoftware.smack.*;
import org.jivesoftware.smack.ConnectionConfiguration.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.*;
import org.jivesoftware.smackx.*;
import org.jivesoftware.smackx.packet.*;
import org.jivesoftware.smackx.provider.*;
import org.jivesoftware.smackx.pubsub.*;
import org.jivesoftware.smackx.pubsub.provider.*;
import org.jivesoftware.smackx.search.*;

import android.util.Log;

import com.fabula.android.timeline.*;
import com.fabula.android.timeline.models.*;
import com.fabula.android.timeline.sync.*;
import com.fabula.android.timeline.utilities.*;

/**
 * Class that handles connection to the XMPP server
 * and retrieving and sending messages to this
 * @author Mads
 *
 */
public class XMPPListener  implements PacketListener, MessageListener, Runnable {
	private XMPPConnection connection;
	private User user;
	private String username;
	
	/**
	 * Construct that merely sets up who the user is.
	 * Proper initalization is then done by sending
	 * @param user
	 */
	public XMPPListener(User user) {
		this.user = user;
	}

	private static void log(Object arg){
		//Log.i("XMPP",arg.toString());
	}

	private static void err(Object arg){
		//Log.i("XMPP ERROR",arg.toString());
	}

	/**
	 * Sets up the XMPP management.
	 */
	private void setup() {
		
		if(!XMPPconnect(Constants.XMPP_SERVER_URL, 5222))return;
		username = user.getUserName();
		username = username.contains("@")?username.substring(0,username.indexOf('@')):username;
		registerNewUser(username,username);
		log("attempting to log in as "+username);
		login(username, username);
		sendMessage("Error 418: I'm a teapot.", username);
		new PubSubListener<Item>(connection, username);
	}

	private boolean XMPPconnect(String server, int port){
		ConnectionConfiguration config = new ConnectionConfiguration(server,port, "Work");
		configure(ProviderManager.getInstance());
		config.setSecurityMode(SecurityMode.disabled);
		config.setSASLAuthenticationEnabled(false);
		connection = new XMPPConnection(config);
		PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
		connect();
		if(!connection.isConnected()){
			err("No connection made");
			return false;
		}
		try{
			connection.addPacketListener(this, filter);
		}catch(Exception e){
			err("Couldn't add packet listener");
			e.printStackTrace();
			return false;
		}
		return true;
	}


	private void login(String userName, String password)
	{
		try {
			connection.login(userName, password);
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			err("Couldn't log in");
			e.printStackTrace();
		}
	}
	
	private void connect(){
		try {
			connection.connect();
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			err("Couldn't connect");
			e.printStackTrace();
		}
	}

	private void disconnect()
	{
		connection.disconnect();
	}

	public void processMessage(Chat chat, Message message)
	{
		return;
	}

	private boolean registerNewUser(String username, String password){
		AccountManager am = new AccountManager(connection);

		if(!am.supportsAccountCreation()){
			err("Account creation not supported on XMPP server");
			return false;
		}

		log("Attempting to create account "+username);
		try {
			Map<String, String> attributes = new HashMap<String, String>();
			attributes.put("username", username);
			attributes.put("password", password);
			attributes.put("email", "test@test.com");
			attributes.put("name", "test test");
			am.createAccount(username, password, attributes);
			log("Successfully created account "+am.getAccountAttribute(username));
		} catch (XMPPException e) {
			// Presumably the account already existed and nothing needs to be done
			log("Account already existed");
			err(e.getMessage());
			return false;
		}
		return true;
	}


	/**
	 * passing incoming messages on for further parsing.
	 * 
	 * @param message
	 */
	public void parseMessage(String message){
		try{
			Scanner parser = new Scanner(message);
			String head = parser.nextLine();
			String creator = parser.nextLine();
			if(creator.equals(DashboardActivity.creator.name)) return;
			log(head);
			log("attempting to parse: \n"+message);
			if(head.equals("EventItem"))parseEventItem(parser, creator);
			if(head.equals("Emotion"))parseEmotion(parser, creator);
			if(head.equals("Timeline"))parseTimeline(parser, creator);
			if(head.equals("MoodEvent"))parseMoodEvent(parser, creator);
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	private void parseMoodEvent(Scanner parser, String creator) {
		String ID = parser.nextLine();
		String toTimeline = parser.nextLine();

		MoodEvent mood = null;
		for(int a=0;a<10&&(mood = (MoodEvent) ServerDownloader.getEvent(ID, toTimeline))==null;a++){
			//System.out.println("Couldn't fetch item from server, "+(9-a)+" retries left");
			try{
				Thread.sleep(10000);
			}
			catch(Exception e){

			}
			//System.out.println("Retrying...");
		}
		if(mood == null){
			//System.out.println("Received notification of shared MoodEvent "+ID);
			//System.out.println("But nothing was found on the server");
			return;
		}

		mood.setCreatorName(creator);

		XMPPManager.storeItem(mood, toTimeline, creator);
	}

	private void parseTimeline(Scanner parser, String creator) {
		//TODO: Remove debug prints
		String ID = parser.nextLine();
		String group = parser.nextLine();
		log("Parsed ID as "+ID);
		log("Attempting to get timeline from server: ");
		Experience timeline = null;
		try{
			for(int a=0;a<10&&(timeline = ServerDownloader.getTimeline(ID))==null;a++){
				err("Couldn't fetch timeline from server, "+(9-a)+" retries left");
				try{
					Thread.sleep(10000);
				}
				catch(Exception e){

				}
				err("Retrying...");
			}
		}
		catch(Exception e){
			e.printStackTrace();
		}
		log("Downloaded from server with group");
		log("SHARING GROUP OBJECT: "+timeline.getSharingGroupObject());
		log("SHARING GROUP: "+timeline.getSharingGroup());
		try{
			timeline.setSharingGroupObject(ServerDownloader.getGroup(group));
		}
		catch(Exception e){
			e.printStackTrace();
		}

		XMPPManager.storeItem(timeline);
	}

	private void parseEmotion(Scanner parser, String creator) {
		String toEvent = parser.nextLine();
		String toTimeline = parser.nextLine();
		String ID = parser.nextLine();
		Emotion emo = null;

		for(int a=0;a<10&&(emo = ServerDownloader.getEmotion(ID, toTimeline, toEvent))==null;a++){
			err("Couldn't fetch item from server, "+(9-a)+" retries left");
			try{
				Thread.sleep(10000);
			}
			catch(Exception e){

			}
			err("Retrying...");
		}

		if(emo == null){
			err("Received notification of shared emotion "+ID);
			err("But nothing was found on the server");
			return;
		}
		else{
			log("Received a "+emo.getEmotionType()+" emotion");
			log(emo.toString());
		}

		XMPPManager.storeItem(emo, toEvent, toTimeline, creator);
	}


	/**
	 * parses incoming messages into events
	 * @param creator 
	 * 
	 * @param message
	 */
	private void parseEventItem(Scanner parser, String creator) {
		String toEvent = parser.nextLine();
		String toTimeline = parser.nextLine();
		String ID = parser.nextLine();
		EventItem item = null;

		for(int a=0;a<2&&(item = ServerDownloader.getEventItem(ID, toTimeline, toEvent))==null;a++){
			err("Couldn't fetch item from server, "+(9-a)+" retries left");
			try{Thread.sleep(10000);
			}catch(Exception e){}
			err("Retrying...");
		}

		if(item == null){
			err("Received notification of shared eventItem "+ID);
			err("But nothing was found on the server");
			return;
		}
		else log("Received a "+item.getClass());
		XMPPManager.storeItem(item, toEvent, toTimeline, creator);
	}


	/**
	 * Sends a message to all the members of a timeline.
	 * 
	 * 
	 * the message should be formatted properly to avoid errors when parsing.
	 * 
	 * @see shareEventItem
	 * @param message
	 * @param target
	 */
	private void sendGroupMessage(String message, Group target){
		if(target==null)return;
		//System.out.println("Sending a group message: \n"+message);
		//System.out.println("To: "+target);
		//System.out.println(target.getMembers().size());
		//System.out.println(target.getMembers());


		for(User user:target.getMembers()){
			//System.out.println("to: "+user.getUserName());
			sendMessage(message, user.getUserName());
		}
	}

	public void processPacket(Packet packet) {

		Message message = (Message) packet;
		if (message.getBody() != null)parseMessage(message.getBody());
	}


	public void run() {
		setup();
	}

	/**
	 * Share an event with other members of the timeline.
	 * 
	 * @param event the event to be shared
	 * @param experience the experience that it is shared in
	 */
	public void shareEvent(BaseEvent event, Experience experience){
		log("Sharing an event");
		if(event instanceof MoodEvent){
			MoodEvent cast = (MoodEvent)event;
			sendGroupMessage("MoodEvent\n" +
					DashboardActivity.creator.name+"\n"+
					cast.getId()+"\n" +
					cast.getExperienceid(),experience.getSharingGroupObject());
		}
		log(event.getEventItems().size()+" eventitems");
		log(event.getEmotionList().size()+" emotions");
		for(EventItem item:event.getEventItems())shareEventItem(item, event, experience);
		for(Emotion emo:event.getEmotionList())shareEmotion(emo, event, experience);
	}

	/**
	 * Share an eventitem with other members of the timeline.
	 * 
	 * @param item
	 * @param event
	 * @param experience
	 */
	public void shareEventItem(EventItem item, BaseEvent event, Experience experience){
		String XMPPString = "EventItem\n"+
				DashboardActivity.creator.name+"\n"+
				event.getId()+"\n"+
				experience.getId()+"\n";
		XMPPString+=item.getId();

		sendGroupMessage(XMPPString, experience.getSharingGroupObject());
	}

	/**
	 * Share an emotion with other members of the timeline
	 * @param item
	 * @param event
	 * @param experience
	 */
	public void shareEmotion(Emotion item, BaseEvent event, Experience experience){

		String XMPPString = "Emotion\n"+
				DashboardActivity.creator.name+"\n"+
				event.getId()+"\n"+
				experience.getId()+"\n"+
				item.getEmotionid();

		sendGroupMessage(XMPPString, experience.getSharingGroupObject());
	}

	/**
	 * Share a timeline
	 * @param timeLine
	 */
	public void shareTimeline(Experience timeLine) {
		try{
			String XMPPString = "Timeline\n"+
					DashboardActivity.creator.name+"\n"+
					timeLine.getId()+"\n"+
					timeLine.getSharingGroupObject().getId();

			sendGroupMessage(XMPPString, timeLine.getSharingGroupObject());
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * Send a message to the given xmpp-account.
	 * 
	 * @param message
	 * @param to
	 */
	public void sendMessage(String message, String to)
	{
		//System.out.println("Sending the message to: "+to);
		to = to.contains("@")?to.substring(0,to.indexOf('@')):to;
		Chat chat = connection.getChatManager().createChat(to+"@"+Constants.XMPP_DOMAIN+"/Smack", this);
		try {
			chat.sendMessage(message);
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}


	private void configure(ProviderManager pm) {

		// The order is the same as in the smack.providers file

		// Private Data Storage
		pm.addIQProvider("query", "jabber:iq:private",
				new PrivateDataManager.PrivateDataIQProvider());
		// Time
		try {
			pm.addIQProvider("query", "jabber:iq:time",
					Class.forName("org.jivesoftware.smackx.packet.Time"));
		} catch (ClassNotFoundException e) {
			System.err
			.println("Can't load class for org.jivesoftware.smackx.packet.Time");
		}

		// Roster Exchange
		pm.addExtensionProvider("x", "jabber:x:roster",
				new RosterExchangeProvider());
		// Message Events
		pm.addExtensionProvider("x", "jabber:x:event",
				new MessageEventProvider());
		// Chat State
		pm.addExtensionProvider("active",
				"http://jabber.org/protocol/chatstates",
				new ChatStateExtension.Provider());
		pm.addExtensionProvider("composing",
				"http://jabber.org/protocol/chatstates",
				new ChatStateExtension.Provider());
		pm.addExtensionProvider("paused",
				"http://jabber.org/protocol/chatstates",
				new ChatStateExtension.Provider());
		pm.addExtensionProvider("inactive",
				"http://jabber.org/protocol/chatstates",
				new ChatStateExtension.Provider());
		pm.addExtensionProvider("gone",
				"http://jabber.org/protocol/chatstates",
				new ChatStateExtension.Provider());

		// XHTML
		pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im",
				new XHTMLExtensionProvider());

		// Group Chat Invitations
		pm.addExtensionProvider("x", "jabber:x:conference",
				new GroupChatInvitation.Provider());
		// Service Discovery # Items
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#items",
				new DiscoverItemsProvider());
		// Service Discovery # Info
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#info",
				new DiscoverInfoProvider());
		// Data Forms
		pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
		// MUC User
		pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user",
				new MUCUserProvider());
		// MUC Admin
		pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin",
				new MUCAdminProvider());
		// MUC Owner
		pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner",
				new MUCOwnerProvider());
		// Delayed Delivery
		pm.addExtensionProvider("x", "jabber:x:delay",
				new DelayInformationProvider());
		pm.addExtensionProvider("delay", "urn:xmpp:delay",
				new DelayInformationProvider());
		// Version
		try {
			pm.addIQProvider("query", "jabber:iq:version",
					Class.forName("org.jivesoftware.smackx.packet.Version"));
		} catch (ClassNotFoundException e) {
			System.err
			.println("Can't load class for org.jivesoftware.smackx.packet.Version");
		}
		// VCard
		pm.addIQProvider("vCard", "vcard-temp", new VCardProvider());
		// Offline Message Requests
		pm.addIQProvider("offline", "http://jabber.org/protocol/offline",
				new OfflineMessageRequest.Provider());
		// Offline Message Indicator
		pm.addExtensionProvider("offline",
				"http://jabber.org/protocol/offline",
				new OfflineMessageInfo.Provider());
		// Last Activity
		pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider());
		// User Search
		pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider());
		// SharedGroupsInfo
		pm.addIQProvider("sharedgroup",
				"http://www.jivesoftware.org/protocol/sharedgroup",
				new SharedGroupsInfo.Provider());

		// JEP-33: Extended Stanza Addressing
		pm.addExtensionProvider("addresses",
				"http://jabber.org/protocol/address",
				new MultipleAddressesProvider());

		// FileTransfer
		pm.addIQProvider("si", "http://jabber.org/protocol/si",
				new StreamInitiationProvider());
		// pm.addIQProvider("query","http://jabber.org/protocol/bytestreams",
		// new BytestreamsProvider());
		// pm.addIQProvider("open","http://jabber.org/protocol/ibb", new
		// OpenIQProvider());
		// pm.addIQProvider("data","http://jabber.org/protocol/ibb", new
		// DataPacketProvider());
		// pm.addIQProvider("close","http://jabber.org/protocol/ibb", new
		// CloseIQProvider());
		// pm.addExtensionProvider("data","http://jabber.org/protocol/ibb", new
		// DataPacketProvider());

		// Privacy
		pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider());

		// SHIM
		pm.addExtensionProvider("headers", "http://jabber.org/protocol/shim",
				new HeadersProvider());
		pm.addExtensionProvider("header", "http://jabber.org/protocol/shim",
				new HeadersProvider());
		// PubSub
		pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub",
				new PubSubProvider());
		pm.addExtensionProvider("create", "http://jabber.org/protocol/pubsub",
				new SimpleNodeProvider());
		pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub",
				new ItemsProvider());
		pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub",
				new ItemProvider());
		pm.addExtensionProvider("subscriptions",
				"http://jabber.org/protocol/pubsub",
				new SubscriptionsProvider());
		pm.addExtensionProvider("subscription",
				"http://jabber.org/protocol/pubsub", new SubscriptionProvider());
		pm.addExtensionProvider("affiliations",
				"http://jabber.org/protocol/pubsub", new AffiliationsProvider());
		pm.addExtensionProvider("affiliation",
				"http://jabber.org/protocol/pubsub", new AffiliationProvider());
		pm.addExtensionProvider("options", "http://jabber.org/protocol/pubsub",
				new FormNodeProvider());
		// PubSub owner
		pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub#owner",
				new PubSubProvider());
		pm.addExtensionProvider("configure",
				"http://jabber.org/protocol/pubsub#owner",
				new FormNodeProvider());
		pm.addExtensionProvider("default",
				"http://jabber.org/protocol/pubsub#owner",
				new FormNodeProvider());
		// PubSub event
		pm.addExtensionProvider("event",
				"http://jabber.org/protocol/pubsub#event", new EventProvider());
		pm.addExtensionProvider("configuration",
				"http://jabber.org/protocol/pubsub#event",
				new ConfigEventProvider());
		pm.addExtensionProvider("delete",
				"http://jabber.org/protocol/pubsub#event",
				new SimpleNodeProvider());
		pm.addExtensionProvider("options",
				"http://jabber.org/protocol/pubsub#event",
				new FormNodeProvider());
		pm.addExtensionProvider("items",
				"http://jabber.org/protocol/pubsub#event", new ItemsProvider());
		pm.addExtensionProvider("item",
				"http://jabber.org/protocol/pubsub#event", new ItemProvider());
		pm.addExtensionProvider("retract",
				"http://jabber.org/protocol/pubsub#event",
				new RetractEventProvider());
		pm.addExtensionProvider("purge",
				"http://jabber.org/protocol/pubsub#event",
				new SimpleNodeProvider());

		// Nick Exchange
		pm.addExtensionProvider("nick", "http://jabber.org/protocol/nick",
				new Nick.Provider());

		// Attention
		pm.addExtensionProvider("attention", "urn:xmpp:attention:0",
				new AttentionExtension.Provider());
	}

	public boolean isConectedToXmppServer() {
		return connection!=null;
	}
}
